Technical Note #218 page # of 5 Finding a Slot for a Driver Macintosh Technical Notes � #218: Finding a Slot for a Driver See also: Inside Macintosh: The Device Manager Technical Note #71: Finding Drivers in the Unit Table Technical Note #108: AddDrive, DrvrInstall and DrvrRemove Written by: Mark Bennett October 1, 1988 This Technical Note presents some considerations and a method for finding a �slot� in the Unit Table for a driver. A general-usage driver provides services to software that might run on a Macintosh at any time, and it is best to install this type of driver at INIT time. Typically, you can implement a general-usage driver by providing a file of type INIT, CDEV, or RDEV which the user can move into the System Folder. This file would contain resources of type 'INIT' and 'DRVR', and when the 'INIT' resource is loaded into memory and executed at INIT time, it installs the driver. Some Driver Training Some difficulty arises in deciding how best to install the driver in the Unit Table. The right decision requires a basic understanding of the Unit Table on the various machines. First, there is the structure of the Unit Table which is a non-relocatable block in the system heap to which the low-memory global UTableBase ( 11C)points.TheUnitTableisacontiguoustableofhandlestoDeviceControlEntry(DCE)records.TheoffsetofahandlewithintheUnitTable(itsslot�)determinestheunitnumberforthedriver, anditalsodeterminestherefnumforthathandlesinceunitnumbersrelatetoreferencenumbersinthefollowingway : refnum = �(unitnumber + 1)IfthehandleataparticularslotisNIL, thenthereisnoDCE, andthusnodriverinstalledforthatrefnum.Thereisanotherlow - memoryglobal, UnitNtryCnt(1D2), which is the count of entries in the Unit Table, and you can use it to determine when to stop searching the Unit Table. Many of the slots in the Unit Table are committed to certain devices since the corresponding reference numbers are committed to those devices. For example, the refnum �3 is reserved for the .Print driver; therefore, unit number �(�3)�1=2 is reserved, which means that the slot at offset 8 (4 bytes per handle * 2) in the Unit Table is reserved for the handle to the DCE of the .Print driver. This is true even if the slot is currently NIL because a lot of the Operating System, Toolbox, and applications make heavy assumptions about the reference numbers of various devices. There are more detailed lists in Inside Macintosh, but here is a summary list of what unit numbers are committed to what device types: Unit Number Range Refnum Range Usage 0 through 11 �1 through �12 Serial, Disk, AppleTalk Printer, etc. 12 through 31 �13 through �32 Desk Accessories 32 through 39 �33 through �40 SCSI Devices 40 through 47 �41 through �48 AppleShare and other reserved for Apple �48 through 63, 127 �49 through �64, �128 Slot drivers � open for other drivers as well � The entry for unit numbers 48 and above requires some explanation. First, on the Macintosh 512Ke and Macintosh Plus, the Unit Table does not go past unit number 47; we will address that later. On the Macintosh SE, the Unit Table is large enough to hold up to unit number 63. On the Macintosh II, the Unit Table is large enough to hold up to unit number 127, but UnitNtryCnt will probably be lower, although not lower than 64. The Driving Force Behind it All Now we need to see what happens when the system installs a driver. The system installs a driver into the Unit Table when it gets a call to the _Open trap. The behavior of _Open depends upon what machine is running, but it is fairly consistent with the exception of Nubus slot drivers on a Macintosh II. The following is a psuedo-code description with an accompanying narrative: IF call is NOT from OpenDeskAcc AND filename does NOT begin with '.' THEN pass control to file system ELSE [ IF driver is for slot device THEN [ explained in narrative below search unit table from 48 through UnitNtryCnt for match IF NOT found THEN call GetNamedResource for driver name type 'DRVR' IF NOT successful THEN call SGetDriver for driver IF NOT successful THEN return error found the driver search unit table from 48 through UnitNtryCnt for NIL DCE handle IF NOT found THEN [ bump up UnitNtryCnt by 4 IF maximum UnitNtryCnt exceeded THEN return error ] found a slot install driver ] ELSE [ search unit table from 0 through UnitNtryCnt for match IF NOT found THEN call GetNamedResource for driver name type 'DRVR' IF NOT successful THEN return error found driver get resource ID of DRVR resource IF unit number not already in use THEN install driver ] otherwise, leave old driver there IF driver NOT already open THEN [ move Drvr fields into corresponding DCE fields IF driver is RAM-based THEN set RAM-based flag in dCtlFlags field of DCE IF driver is for slot device THEN call SFindDevBase and put result into DCE ] IF driver needsLock THEN lock driver and DCE IF driver NOT already open OR called from OpenDeskAcc THEN call Open routine of driver ] First, _Open checks to see if the call is from _OpenDeskAcc. If it is not, it checks to see if the filename in the parameter block begins with a period (.). If the filename does not begin with a period, control is passed to the file system. Otherwise, if the machine has NuBus slots, a check is made to see if the driver is for a slot device. This check is made by seeing if bit 9 of the _Open trap word was set. If so, then if bit 0 of the ioFlags word is set or the ioSlot field of the parameter block is not zero, then the driver is assumed to be for a slot device. If the driver is for a slot device, the Unit Table is searched, starting at unit number 48 and ending at UnitNtryCnt. If the dCtlSlot, dCtlSlotID, and dCtlExtDev fields of none of the DCEs of the unit numbers match the fields of the parameter block, then _GetNamedResource is called, using the name of the driver and type 'DRVR'. If that fails, then _SGetDriver is called to load the driver from the card�s ROM. If that attempt fails, an error is reported. In the case where one succeeds, then once the new driver has been loaded, the Unit Table is searched, starting at unit number 48 and ending at UnitNtryCnt, for an unused (NIL) slot. If none are found, the value of UnitNtryCnt is bumped up by 4. If the value exceeds 128, then an error is reported, otherwise the newly created spot is used for the driver. If the driver is not for a slot device, or has been determined to be a desk accessory, the Unit Table is searched, starting at unit number 0 and ending at UnitNtryCnt. If the driver name for none of the installed drivers in the table match the filename of the parameter block, then _GetNamedResource is called, using the name of the driver and type 'DRVR'. If that attempt fails, an error is reported. If it succeeds, the ID of the resource is assumed to be the unit number of the driver and is mapped into the equivalent refnum. If the slot for that refnum is already occupied, then the driver that is already there gets left there. Once the slot device or non-slot device driver is installed or it has been determined that a driver already occupies the slot in the Unit Table, the driver is checked to see if it has already been opened. If it hasn�t, the driver is checked to see if it is RAM-based or not and the DCtlFlags field of the driver�s DCE is set accordingly, along with being combined with the rest of the DrvrFlags field of the driver. The DrvrDelay, DrvrEMask, and DrvrMenu fields from the driver header are also moved into the corresponding fields of the DCE. If the driver is for a slot device, _SFindDevBase is called for the slot and ID of the driver�s device and the result is put into the DCE. Once the DCE fields have been filled in, or it was determined the driver was already opened, the driver and DCE are locked if needed, the permissions are checked, with an error returned if they are incorrect, and the _Open routine called if the driver was not already open or was a desk accessory. That�s Great, but What are You Driving At? Well, we�re getting there. If you are trying to provide a non-slot driver that can be installed at INIT time and then be used later by other software, the best method for finding a unit number in the Unit Table is the way the _Open trap in the Macintosh II ROM finds a unit number in the Unit Table for slot device drivers. Unfortunately, you may want to run on machines other than the Macintosh II, and it would be a bit kludgy to fake being a slot device driver, so you cannot just call _Open and pretend to be a slot device driver. In addition, it is likely that you may wish that the driver�s open routine not be called until it is actually �_Opened� by software that really wants to use it; therefore, your INIT code must mimic the pertinent code of the _Open routine. The first action should be to call _GetNamedResource with the name of the driver and its resource type (typically 'DRVR', although it is not required since you are loading it yourself). The resource that is your driver must have the system heap bit set in its resource attributes so that it is loaded into the system heap where it can continue to exist, long after the INIT has gone away. Note that if for some incredible reason, your driver is greater than 16K, you might want to include a 'sysz' resource in order to increase the available space in the system heap. Next, you must call _DetachResource with the handle to your resource so that it is not removed when your INIT file is closed. Now you are ready to find a slot in the Unit Table for your driver. First check if unit numbers 48 and higher are even available. This can be done by checking UnitNtryCnt. If UnitNtryCnt is 48, then that poses a bit of a problem in that there are no empty slots available in the Unit Table. You can rectify this, however, by resizing the Unit Table. We accomplish that task by creating a new Unit Table that is larger than the old one. Here�s how: First, create a new, non-relocatable block in the system heap that is the new size you want and clear it to zeroes. The following assembly code fragment gives an example: MOVE.W D1,D0 ;D1 = requested # slots MULU.W #4,D0 ;turn it into size _NewPtr,SYS,CLEAR ;create clear block in system heap BNE Error ;check for errors! Next, we must copy the contents of the old Unit Table into the new Unit Table and then point UTableBase to the new Unit Table and adjust the value of UnitNtryCnt. While we are doing all of that, it would be most inconvenient if an interrupt were to occur. Therefore, we must turn off interrupts during the process. The following assembly code fragment, which would follow the previous code, gives an example: MOVE SR,-(SP) ;save old interrupt status MOVE #SCCLockOut,SR ;disable interrupts MOVEA.L A0,A1 ;A0 (address new unit table) -> A1 MOVEA.L UTableBase,A0 ;old unit table -> A0 MOVE.W UnitNtryCnt,D0 ;number of entries -> D0 MULU.W #4,D0 ;size of old table -> D0 _BlockMove ;copy old table to new table _DisposPtr ;get rid of old table MOVE.L A1,UTableBase ;make us new unit table MOVE.W D1,UnitNtryCnt ;update number of entries MOVE (SP)+,SR ;restore old interrupt status We suggest that if UnitNtryCnt was originally 48 that you increase it to 64, adjusting the table size as shown. At this point, with the Unit Table resized or already at a size to hold more than 48 unit numbers, it may be searched for an empty slot, starting at unit number 48 and ending at UnitNtryCnt. If, in the case where the Unit Table already held more than 48 unit numbers, no empty slot was found, then you might be able to expand the Unit Table as was described previously. This time, however, the process can be a bit more complicated. It could very well be that the Unit Table itself is already larger than what UnitNtryCnt would indicate, either because the machine is a Macintosh II or someone else has changed things ahead of your INIT. The best course of action would be to call _GetPtrSize on the Unit Table, divide the result by 4, rounding down, and compare that to UnitNtryCnt. If UnitNtryCnt is lower than that result, you can bump up UnitNtryCnt by some amount that would keep it below or equal to what the size of the Unit Table could handle. A number like 4 is good because it reduces the need for someone else to do the same check later without also making the search for a given driver take exceptionally long if it is not present in the Unit Table. Once UnitNtryCnt has been bumped up, then you know you have an empty slot waiting. If UnitNtryCnt was already equal to the size of the Unit Table divided by 4, then you should expand the Unit Table as described previously, choosing a size around 16 or 32 bytes greater than the old size. Remember to always check the result of the _NewPtr call; it would be catastrophic to copy the old Unit Table into the low-memory global area. Driving it Home Finally, once you have found a slot in the Unit Table for the driver, call _DriverInstall with the corresponding refnum and pointer to the driver. This will create a DCE for the driver and set up the correct refnum in the DCE. Next, move the handle to the driver into the dCtlDriver field of the DCE. Then move the DrvrFlags, DrvrDelay, DrvrEMask, and DrvrMenu fields of the driver header into the dCtlFlags, dCtlDelay, dCtlEMask, and dCtlMenu fields of the DCE and set the dRamBased bit in the dCtlFlags field of the DCE. That�s it!